package com.cgmnx.admin.common.util;


import com.cgmnx.admin.common.util.json.DateJsonDeserializer;
import com.cgmnx.admin.common.util.json.DateJsonSerializer;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.common.base.Splitter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.util.Date;


@Slf4j
public class JacksonMapper {
    private final static ObjectMapper objectMapper = new ObjectMapper();

    private static Version version = new Version(1, 1, 1, "",
            "xxx", "cn.xxx");

    static {
        objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
        objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
        objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        objectMapper.disable(SerializationFeature.WRITE_NULL_MAP_VALUES);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

        SimpleModule module = new SimpleModule("DateTimeModule", version);
        module.addSerializer(Date.class, new DateJsonSerializer());
        module.addDeserializer(Date.class, new DateJsonDeserializer());

        objectMapper.registerModule(module);

    }

    private JacksonMapper() {
        throw new UnsupportedOperationException();
    }

    public static ObjectMapper getObjectMapperInstance() {
        return objectMapper;
    }

    public static String encode(Object obj) throws IOException {
        if (obj == null) {
            //防止为null时返回"null"
            return null;
        }
        return objectMapper.writeValueAsString(obj);
    }

    /**
     * 序列化为JSON（不抛出异常，用于打印日志等不关心异常的场景，其他业务场景请使用
     *
     * @param obj
     * @return
     */
    public static String encodeQuietly(Object obj) {
        if (obj == null) {
            return null;
        }
        try {
            return encode(obj);
        } catch (IOException e) {
            log.error("序列化失败, obj:{}, reason:{}", obj, e.getMessage(), e);
            return null;
        }
    }

    public static <T> T decode(String json, Class<T> clazz) throws IOException {
        return objectMapper.readValue(json, clazz);
    }

    public static JsonNode decode(String json) throws IOException {
        return objectMapper.readTree(json);
    }

    public static <T> T decode(JsonNode jsonNode, Class<T> clazz) throws IOException {
        return objectMapper.readValue(objectMapper.treeAsTokens(jsonNode), clazz);
    }

    public static <T> T decode(String json, ParameterizedType parameterizedType) throws IOException {
        return (T) objectMapper.readValue(json, convertToJavaType(parameterizedType));
    }


    public static <T> T decode(String json, String path, Class<T> valueType) throws IOException {
        JsonNode node = objectMapper.readTree(json);
        Iterable<String> names = Splitter.on('.').split(path);
        for (String name : names) {
            node = node.path(name);
        }

        if (node.isMissingNode()) {
            return null;
        } else {
            return (T) objectMapper.reader(valueType).readValue(node);
        }
    }

    private static <T> JavaType convertToJavaType(ParameterizedTypeReference<T> typeReference) {
        TypeFactory typeFactory = TypeFactory.defaultInstance();
        return typeFactory.constructType(typeReference.getType());
    }

    private static JavaType convertToJavaType(ParameterizedType parameterizedType) {
        TypeFactory typeFactory = TypeFactory.defaultInstance();
        return typeFactory.constructType(parameterizedType);
    }
}
